package com.lee.common.transaction;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.*;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionCallback;

import java.lang.reflect.UndeclaredThrowableException;

/**
 * 编程式事务控制模块,用于更精确控制事务执行
 *
 * @author Lee HN
 * @date 2020/11/11 14:45
 */
@Slf4j
@Component
@AllArgsConstructor
public class TransactionOps {

    private final PlatformTransactionManager transactionManager;

    private static final TransactionDefinition definition = TransactionDefinition.withDefaults();

    public <T> T execute(TransactionCallback<T> action) throws TransactionException {
        return execute(action, definition);
    }

    public <T> T execute(TransactionCallback<T> action, Isolation isolation) throws TransactionException {
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        definition.setIsolationLevel(isolation.value());
        return execute(action, definition);
    }

    public <T> T execute(TransactionCallback<T> action, Isolation isolation, Propagation propagation) throws TransactionException {
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        definition.setPropagationBehavior(propagation.value());
        definition.setIsolationLevel(isolation.value());
        return execute(action, definition);
    }

    public <T> T execute(TransactionCallback<T> action, TransactionDefinition definition) throws TransactionException {
        TransactionStatus status = this.transactionManager.getTransaction(definition);
        T result;
        try {
            result = action.doInTransaction(status);
        } catch (Exception | Error ex) {
            // Transactional code threw application exception -> rollback
            rollbackOnException(status, ex);
            throw ex;
        } catch (Throwable ex) {
            // Transactional code threw unexpected exception -> rollback
            rollbackOnException(status, ex);
            throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
        }
        this.transactionManager.commit(status);
        return result;
    }

    /**
     * Perform a rollback, handling rollback exceptions properly.
     *
     * @param status object representing the transaction
     * @param ex     the thrown application exception or error
     * @throws TransactionException in case of a rollback error
     */
    private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException {
        log.debug("Initiating transaction rollback on application exception", ex);
        try {
            this.transactionManager.rollback(status);
        } catch (TransactionSystemException ex2) {
            log.error("Application exception overridden by rollback exception", ex);
            ex2.initApplicationException(ex);
            throw ex2;
        } catch (RuntimeException | Error ex2) {
            log.error("Application exception overridden by rollback exception", ex);
            throw ex2;
        }
    }

}
